/*******************************************************************************
 * Copyright (c) 2001, 2002 Boris Pruessmann and others. All rights reserved.
 * This program and the accompanying materials are made available under the
 * terms of the Common Public License v0.5 which accompanies this distribution,
 * and is available at http://www.eclipse.org/legal/cpl-v05.html
 * 
 * Contributors: Boris Pruessmann - Initial implementation.
 ******************************************************************************/
package net.sourceforge.perforce.core.internal.api;

import java.util.Stack;

import net.sourceforge.perforce.api.IPerforceCommand;
import net.sourceforge.perforce.core.PerforceCorePlugin;
import net.sourceforge.perforce.core.api.IResolveResult;

import org.apache.regexp.RE;
import org.apache.regexp.RECompiler;
import org.apache.regexp.REProgram;
import org.apache.regexp.RESyntaxException;
import org.eclipse.core.runtime.IStatus;
import org.eclipse.core.runtime.MultiStatus;

/**
 * ClientUser object for the 'p4 resolve' command.
 * 
 * @version $Revision: 488 $
 * @author <a href="mailto:bpruessmann@users.sourceforge.net">Boris
 *         Pruessmann</a>
 */
public class Resolve extends AbstractPerforceCommand implements IPerforceCommand {
  // ---------------------------------------------------------------------------
  // Private Constants

  /** Regular Expression: resolve skipped. */
  private static final REProgram RE_RESOLVE_SKIPPED;
  /** Regular Expression: matching result without revisions */
  private static final REProgram RE_WITHOUT_REVISIONS;
  /** Regular Expression: matches result with one revision */
  private static final REProgram RE_WITH_ONE_REVISION;
  /** Regular Expression: matches the rest. */
  private static final REProgram RE_WITH_BOTH_REVISIONS;

  // -------------------------------------------------------------------- Static
  // Initializer Block

  static {
    try {
      RECompiler compiler = new RECompiler();
      RE_RESOLVE_SKIPPED = compiler.compile("^(//.*) - resolve skipped.$"); //$NON-NLS-1$
      RE_WITHOUT_REVISIONS = compiler.compile("^(.*) - (.*) (//.*)$"); //$NON-NLS-1$
      RE_WITH_ONE_REVISION = compiler.compile("^(.*) - (.*) (//.*)(#([0-9]*))$");//$NON-NLS-1$
      RE_WITH_BOTH_REVISIONS = compiler.compile("^(.*) - (.*) (//.*)(#([0-9]*),#([0-9]*))$"); //$NON-NLS-1$
    } catch (RESyntaxException e) {
      throw new RuntimeException(e.getMessage());
    }
  }

  // ---------------------------------------------------------------------------
  // Private Variables

  /** Stores the operation's result. */
  private Stack<IResolveResult> result = new Stack<IResolveResult>();

  // --------------------------------------------------------------- Public
  // Static Factory Methods

  /**
   * Convenience method for creating instances of Resolve.
   * 
   * @return the new Reopen instance.
   */
  public static final Resolve newInstance() {
    MultiStatus status = new MultiStatus(PerforceCorePlugin.ID, IStatus.OK, "", null);
    return new Resolve(status);
  }

  // ------------------------------------------------------------------------
  // Private Constructors

  /**
   * Creates a new instance.
   * 
   * @param status the MultiStatus instance.
   */
  private Resolve(MultiStatus status) {
    super(status);
  }

  // -------------------------------------------------------- Public Methods
  // (IClientUserDelegate)

  @Override
  public void outputInfo(char level, String infoString) {
    if (level != '0') {
      return;
    }

    RE re = new RE(RE_RESOLVE_SKIPPED);
    if (re.match(infoString)) {
      handleResolveSkipped(re);
      return;
    }

    re = new RE(RE_WITH_BOTH_REVISIONS);
    if (re.match(infoString)) {
      handleWithRevision(re);
      return;
    }

    re = new RE(RE_WITH_ONE_REVISION);
    if (re.match(infoString)) {
      handleWithRevision(re);
      return;
    }

    re = new RE(RE_WITHOUT_REVISIONS);
    if (re.match(infoString)) {
      handleWithoutRevisions(re);
      return;
    }
  }

  // ----------------------------------------------------------- Public Methods
  // (IPerforceCommand)

  public String getCommand() {
    return "resolve"; //$NON-NLS-1$
  }

  @Override
  public IStatus getStatus() {
    return getMultiStatus();
  }

  //----------------------------------------------------------------------------
  // -- Public Methods

  /**
   * Returns the result of the operation.
   * 
   * @return array of result objects.
   */
  public IResolveResult[] getResult() {
    return result.toArray(new IResolveResult[result.size()]);
  }

  //----------------------------------------------------------------------------
  // - Private Methods

  /**
   * Handle infostring matching regular expression RE_RESOLVE_SKIPPED.
   * 
   * @param re the regular expression.
   */
  private void handleResolveSkipped(RE re) {
    // resolve skipped comes as addition to previous output.
    Result resolveResult = (Result) result.peek();
    resolveResult.setIsSkipped(true);

  }

  /**
   * Handle infostring matching regular expression RE_WITH_BOTH_REVISIONS and
   * RE_WITH_ONE_REVISION.
   * 
   * @param re the regular expression.
   */
  private void handleWithRevision(RE re) {
    Result resolveResult = new Result();
    resolveResult.setLocalFilename(re.getParen(1));
    resolveResult.setDepotFilename(re.getParen(3));
    resolveResult.setRevision(new Integer(re.getParen(5)));

    if (result.isEmpty()) {
      result.push(resolveResult);
    } else {
      Result top = (Result) result.peek();
      if (!top.getDepotFilename().equals(resolveResult.getDepotFilename())) {
        result.push(resolveResult);
      }
    }
  }

  /**
   * Handle infostring matching regular expression RE_WITHOUT_REVISIONS.
   * 
   * @param re the regular expression.
   */
  private void handleWithoutRevisions(RE re) {
    Result resolveResult = new Result();
    resolveResult.setLocalFilename(re.getParen(1));
    resolveResult.setDepotFilename(re.getParen(3));

    if (result.isEmpty()) {
      result.push(resolveResult);
    } else {
      Result top = (Result) result.peek();
      if (!top.getDepotFilename().equals(resolveResult.getDepotFilename())) {
        result.push(resolveResult);
      }
    }

  }

  // ----------------------------------------------------------------------
  // Private Nested Classes

  /**
   * Implementation of the IResolveResult interface. Stores information provided
   * by the 'p4 resolve' command.
   * 
   * @see IResolveResult
   */
  public class Result implements IResolveResult {
    // -----------------------------------------------------------------------
    // Private Variables

    /** Stores the depot filename. */
    private String depotFilename;
    /** Stores the filename. */
    private String filename;
    /** Stores the revision. */
    private Integer revision;
    /** Indicates that the file was skpped. */
    private boolean skipped;

    //--------------------------------------------------------------------------
    // Public Methods

    /** @see IResolveResult#getDepotFilename() */
    public String getDepotFilename() {
      return depotFilename;
    }


    /** @see IResolveResult#getLocalFilename() */
    public String getLocalFilename() {
      return filename;
    }

    /** @see IResolveResult#getRevision() */
    public Integer getRevision() {
      return revision;
    }

    /** @see IResolveResult#isSkipped() */
    public boolean isSkipped() {
      return skipped;
    }

    //--------------------------------------------------------------------------
    // ------- Methods

    /**
     * Sets the depot filename.
     * 
     * @param newDepotFilename the new depot filename.
     */
    void setDepotFilename(String newDepotFilename) {
      depotFilename = newDepotFilename;
    }

    /**
     * Sets the filename.
     * 
     * @param filename The filename to set
     */
    void setLocalFilename(String filename) {
      this.filename = filename;
    }

    /**
     * Sets the revision to resolve.
     * 
     * @param theRevision the revision value.
     */
    void setRevision(Integer theRevision) {
      revision = theRevision;
    }

    /**
     * Sets the skipped value.
     * 
     * @param isSkipped true or false.
     */
    void setIsSkipped(boolean isSkipped) {
      skipped = isSkipped;
    }
  }
}
